package com.csp.mingyue.sas.endpoint;

import java.io.IOException;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 登录端点
 *
 * @author Strive
 * @date 2022/8/1
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/password")
public class LoginEndpoint {

  private final OAuth2AuthorizationService tokenService;

  private final RegisteredClientRepository registeredClientRepository;

  private final OAuth2AccessTokenGenerator tokenGenerator = new OAuth2AccessTokenGenerator();

  private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
      new OAuth2AccessTokenResponseHttpMessageConverter();

  private final OAuth2RefreshTokenGenerator refreshTokenGenerator =
      new OAuth2RefreshTokenGenerator();

  @SneakyThrows
  @GetMapping("/login")
  public void login(
      HttpServletResponse response, HttpServletRequest request, String username, String password) {

    RegisteredClient client = registeredClientRepository.findByClientId("baidu");

    // 校验用户名密码
    if (!"mingyue".equals(username) || !"123456".equals(password)) {
      throw new Exception("用户名或密码错误");
    }

    UsernamePasswordAuthenticationToken authenticationToken =
        new UsernamePasswordAuthenticationToken(username, password);

    DefaultOAuth2TokenContext.Builder builder =
        DefaultOAuth2TokenContext.builder()
            .registeredClient(client)
            .principal(authenticationToken)
            .tokenType(OAuth2TokenType.ACCESS_TOKEN)
            .authorizationGrantType(AuthorizationGrantType.PASSWORD);

    OAuth2Token generatedAccessToken = this.tokenGenerator.generate(builder.build());
    OAuth2AccessToken accessToken =
        new OAuth2AccessToken(
            OAuth2AccessToken.TokenType.BEARER,
            generatedAccessToken.getTokenValue(),
            generatedAccessToken.getIssuedAt(),
            generatedAccessToken.getExpiresAt(),
            builder.build().getAuthorizedScopes());

    OAuth2Authorization.Builder authorizationBuilder =
        OAuth2Authorization.withRegisteredClient(client)
            .principalName("baidu")
            .principalName(authenticationToken.getName())
            .authorizationGrantType(AuthorizationGrantType.PASSWORD);

    if (generatedAccessToken instanceof ClaimAccessor) {
      authorizationBuilder.token(
          accessToken,
          (metadata) ->
              metadata.put(
                  OAuth2Authorization.Token.CLAIMS_METADATA_NAME,
                  ((ClaimAccessor) generatedAccessToken).getClaims()));
    } else {
      authorizationBuilder.accessToken(accessToken);
    }

    OAuth2Token generatedRefreshToken =
        this.refreshTokenGenerator.generate(
            builder
                .tokenType(OAuth2TokenType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .build());
    authorizationBuilder.refreshToken((OAuth2RefreshToken) generatedRefreshToken);
    OAuth2Authorization authorization = authorizationBuilder.build();
    tokenService.save(authorization);

    Map<String, Object> additionalParameters = new HashMap<>();

    additionalParameters.put("license", "mingyue");

    OAuth2AccessTokenAuthenticationToken oAuth2AccessTokenAuthenticationToken =
        new OAuth2AccessTokenAuthenticationToken(
            client,
            authenticationToken,
            accessToken,
            (OAuth2RefreshToken) generatedRefreshToken,
            additionalParameters);
    sendAccessTokenResponse(response, oAuth2AccessTokenAuthenticationToken);
  }

  @GetMapping("/info")
  public OAuth2Authorization info(String token) {
    return tokenService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
  }

  private void sendAccessTokenResponse(HttpServletResponse response, Authentication authentication)
      throws IOException {

    OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
        (OAuth2AccessTokenAuthenticationToken) authentication;

    OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
    OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
    Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();

    OAuth2AccessTokenResponse.Builder builder =
        OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
            .tokenType(accessToken.getTokenType())
            .scopes(accessToken.getScopes());
    if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
      builder.expiresIn(
          ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
    }
    if (refreshToken != null) {
      builder.refreshToken(refreshToken.getTokenValue());
    }
    if (!CollectionUtils.isEmpty(additionalParameters)) {
      builder.additionalParameters(additionalParameters);
    }
    OAuth2AccessTokenResponse accessTokenResponse = builder.build();
    ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
    this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
  }
}
